import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.Scanner;

import javax.swing.*;

/*
 * Z buffering appears to work fine on solid cubes, most of the commands are in.
 * However, z buffering was not working with the polyChrome.txt file (still trying to figure out
 * what is going on there).
 * 
 * Additionally, and this will show when you run the program: if you hit 'p' or 'o' (which should shift
 * perspective) the panel goes blank (at least with my test files) and the image does not redraw, even 
 * with further attempts to rotate.  All matrices are initialized as identity matrices, so it should not
 * be an accidental zeroing of values.
 */

//TODO add ortho -- in but not working right yet
//TODO add frustum -- in but not working right yet
//TODO serious bug: attempting to use 'o' 'p' to shift view deletes image entirely.


public class PaintPanel458 extends JPanel implements KeyListener {

	/**
	 *  Generated serial version id for serializing objects of this type.  Not recommended. 
	 */
	private static final long serialVersionUID = 6416861567335041861L;
	
	private final static int DEGREE_SHIFT = 3;
	private final static int MAX_RGB = 255;
	private final static double UNIT_SCALE = .5;
	
    int width;
    int height;
    int imageSize;
    int[] pixels;    
    double[][] z_buf;
    
    private int red = MAX_RGB;
    private int green = MAX_RGB;
    private int blue = MAX_RGB;
    
    private double left;
    private double right;
    private double top;
    private double bottom;
    private double near;
    private double far;
    
    private int rotx = 0;
    private int roty = 0;
    private Matrix CTM = new Matrix();
    private Matrix ortho = new Matrix();
    private Matrix frustum = new Matrix();
    private Matrix lookat = new Matrix();
    private Scanner infile;
    private boolean file_is_defined = false;
    private String userFile;
    // ortho - true, frustum - false
    private boolean ortho_view = true;
    
    PaintPanel458() {
        setPreferredSize(new Dimension(512,512));
        setFocusable(true);
        addKeyListener(this);
    }
    
    void drawPixel(int x, int y, double z, int r, int g, int b) {
    	if(x < width && x >= 0 && y < height && y >= 0 && z <= z_buf[x][y]) {
    		z_buf[x][y] = z; //TODO figure out why z_buf is messing up drawings.
    		pixels[(height-y-1)*width*3+x*3] = r;
    		pixels[(height-y-1)*width*3+x*3+1] = g;
    		pixels[(height-y-1)*width*3+x*3+2] = b;
    	}   // bound checking rolled in with hidden surface removal
    }

    private boolean getInput() {
    	if(!file_is_defined) { 
    		Scanner console = new Scanner(System.in);
    		System.out.println("Please enter the path and name of the file to render.");
    		userFile = console.next();
    		console.close();
    	}
       	try {
       		infile = new Scanner(new File(userFile));
       		file_is_defined = true;
       		return true;
		} catch (FileNotFoundException e) {
			System.out.println("File not found.  Exiting program.");
		} catch (NoSuchElementException v) {
			System.out.println("File contains bad formatting.  Exiting program.");
		}
       	return false;
    }
    
    void createImage() {
    	if(getInput())
			while(infile.hasNextLine()) {
				Scanner readLine = new Scanner(infile.nextLine());
				switch (readLine.next()) {
				case "DIM":
					setDim(readLine.nextInt(), readLine.nextInt());
			        imageSize = width * height;
			        z_buf = new double[width][height];
			        pixels = new int[imageSize * 3];
			        for(int i = 0; i < pixels.length; i++) pixels[i] = MAX_RGB;
			        for(int j = 0; j < height; j++) {
			        	for(int k = 0; k < width; k++) {
			        		z_buf[k][j] = Double.MAX_VALUE; //TODO check against frustum/ortho for perspective
			        	}
			        }
					break;
				case "RGB":
					red = convertRGB(readLine.nextDouble());
					green= convertRGB(readLine.nextDouble());
					blue= convertRGB(readLine.nextDouble());
					break;
				case "LINE":
					double x1 = readLine.nextDouble(); double y1 = readLine.nextDouble(); double z1 = readLine.nextDouble();
					double x2 = readLine.nextDouble(); double y2 = readLine.nextDouble(); double z2 = readLine.nextDouble();
					Vector v1l = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(x1, y1, z1, 1))));
					Vector v2l = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(x2, y2, z2, 1))));
					drawLine(v1l, v2l);
					break;
				case "TRI":
					double tx1 = readLine.nextDouble(); double ty1 = readLine.nextDouble(); double tz1 = readLine.nextDouble();
					double tx2 = readLine.nextDouble(); double ty2 = readLine.nextDouble(); double tz2 = readLine.nextDouble();
					double tx3 = readLine.nextDouble(); double ty3 = readLine.nextDouble(); double tz3 = readLine.nextDouble();
					Vector v1 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(tx1, ty1, tz1, 1))));
					Vector v2 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(tx2, ty2, tz2, 1))));
					Vector v3 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(tx3, ty3, tz3, 1))));
					drawTri(v1, v2, v3);
					break;
				case "LOAD_IDENTITY_MATRIX":
					CTM = new Matrix();
					break;
				case "ROTATEX":
					CTM = rotX(readLine.nextDouble()).multiply(CTM);
					break;
				case "ROTATEY":
					CTM = rotY(readLine.nextDouble()).multiply(CTM);
					break;
				case "TRANSLATE":
					CTM = translateMatrix(readLine.nextDouble(), readLine.nextDouble(), readLine.nextDouble()).multiply(CTM);
					break;
				case "SCALE":
					CTM = scaleMatrix(readLine.nextDouble(), readLine.nextDouble(), readLine.nextDouble()).multiply(CTM);
					break;
				case "WIREFRAME_CUBE":
					drawWFCube();
					// TODO: uncomment following to see the current transformation matrix.
					//System.out.println(CTM);
					break;
				case "FRUSTUM":
					left = readLine.nextDouble(); right = readLine.nextDouble();
					bottom = readLine.nextDouble(); top = readLine.nextDouble();
					near = readLine.nextDouble(); far = readLine.nextDouble();
					setFrustum();
					break;
				case "ORTHO":
					left = readLine.nextDouble(); right = readLine.nextDouble();
					bottom = readLine.nextDouble(); top = readLine.nextDouble();
					near = readLine.nextDouble(); far = readLine.nextDouble();
					setOrtho();
					break;
				case "LOOKAT":
					setLookAt(readLine.nextDouble(), readLine.nextDouble(), readLine.nextDouble(),
							readLine.nextDouble(), readLine.nextDouble(), readLine.nextDouble(),
							readLine.nextDouble(), readLine.nextDouble(), readLine.nextDouble());
					break;
				case "SOLID_CUBE":
					drawSolidCube();
					break;
				default:
					System.err.println("line without proper formatting");
				}
				readLine.close();
			}
    }
    
    private void setDim(final int w, final int h) {
    	width = w;
    	height = h;
    }
    
    private void setLookAt(double eyeX, double eyeY, double eyeZ, 
    		double atX, double atY, double atZ, double upX, double upY, double upZ) {
    	Vector eye = new Vector(eyeX, eyeY, eyeZ, 0);
    	Vector at = new Vector(atX, atY, atZ, 0);
    	Vector up = new Vector(upX, upY, upZ, 0);
    	ArrayList<Vector> build = new ArrayList<Vector>();
    	Vector n = Vector.normalize(eye.minus(at));
    	Vector u = Vector.normalize(up.cross(n));
    	Vector v = Vector.normalize(n.cross(u));
    	Vector t = new Vector(0, 0, 0, 1);
    	build.add(u); build.add(v); build.add(n); build.add(t);
    	lookat = new Matrix(build); // built by rows.
    	lookat = Matrix.transpose(lookat); // convert to columns.
    	lookat = lookat.multiply(translateMatrix(-eyeX, -eyeY, -eyeZ));
    }
    
    private void setFrustum() {
		ArrayList<Vector> fbuild = new ArrayList<Vector>();
		fbuild.add(new Vector((2 * near) / (right - left), 0, (right + left) / (right - left), 0));
		fbuild.add(new Vector(0, (2 * near) / (top - bottom), (top + bottom) / (top - bottom), 0));
		fbuild.add(new Vector(0, 0, -(far + near) / (far - near), -(2 * far * near) / (near - far)));
		fbuild.add(new Vector(0, 0, -1, 0));
		frustum = new Matrix(fbuild);
    }
    
    private void setOrtho() {
    	ArrayList<Vector> obuild = new ArrayList<Vector>();
		obuild.add(new Vector(2 / (right - left), 0, 0, -(right + left) / (right - left)));
		obuild.add(new Vector(0, 2 / (top - bottom), 0, -(top + bottom) / (top - bottom)));
		obuild.add(new Vector(0, 0, 2 / (near - far), -(far + near) / (far - near)));
		obuild.add(new Vector(0, 0, 0, 1));
		ortho = new Matrix(obuild);
    }
    
    private void drawTri(final Vector iv1, final Vector iv2, final Vector iv3) {
		Vector v1, v2, v3;

    	v1 = lookat.multiply(iv1);
    	v2 = lookat.multiply(iv2);
    	v3 = lookat.multiply(iv3);
    	
		if(ortho_view) {
			v1 = ortho.multiply(v1);
			v2 = ortho.multiply(v2);
			v3 = ortho.multiply(v3);
		} else {
			v1 = frustum.multiply(v1);
			v2 = frustum.multiply(v2);
			v3 = frustum.multiply(v3);
		}

    	ArrayList<Vector> sideA = traceLine(v1, v2);
    	ArrayList<Vector> sideB = traceLine(v2, v3);
    	ArrayList<Vector> sideC = traceLine(v3, v1);
    	
    	ArrayList<Vector> allSides = new ArrayList<Vector>();
    	allSides.addAll(sideA);
    	allSides.addAll(sideB);
    	allSides.addAll(sideC);
    	
    	Collections.sort(allSides, new PointCompare());
    	
    	for(int i = 0; i < allSides.size() - 1; i++) {
    		if(allSides.get(i).y == allSides.get(i+1).y) {
    			int startX = (int) Math.min(allSides.get(i).x, allSides.get(i+1).x);
    			int endX = (int) Math.max(allSides.get(i).x, allSides.get(i+1).x);
    			for(int j = startX; j <= endX; j++) {
    				drawPixel((int) j, (int) allSides.get(i).y, allSides.get(i).z, red, green, blue);
    			}
    		} else {
    			drawPixel((int) allSides.get(i).x, (int) allSides.get(i).y, allSides.get(i).z, red, green, blue);
    			drawPixel((int) allSides.get(i + 1).x, (int) allSides.get(i + 1).y, allSides.get(i + 1).z, red, green, blue);
    		}
    		
    	}
    }
    
    private void drawWFCube() {
    	Vector v1 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, 1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // top, near, left point
    	Vector v2 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, 1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // top, near, right point
    	Vector v3 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, -1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // bottom, near, right point
    	Vector v4 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, -1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // bottom, near, left point
    	Vector v5 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, 1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // top, far, left point
    	Vector v6 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, 1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // top, far, right point
    	Vector v7 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, -1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // bottom, far, right point
    	Vector v8 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, -1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // bottom, far, left point
    	
    	//v1-v2, v1-v4, v1-v5
    	drawLine(v1, v2);
    	drawLine(v1, v4);
    	drawLine(v1, v5);
    	//v3-v2, v3-v4, v3-v7
    	drawLine(v3, v2);
    	drawLine(v3, v4);
    	drawLine(v3, v7);
    	//v6-v2, v6-v5, v6-v7
    	drawLine(v6, v2);
    	drawLine(v6, v7);
    	drawLine(v6, v5);
    	//v8-v4, v8-v7, v8-v5
    	drawLine(v8, v7);
    	drawLine(v8, v4);
    	drawLine(v8, v5);
    }

    private void drawSolidCube() {
    	Vector v1 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, 1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // top, near, left point
    	Vector v2 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, 1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // top, near, right point
    	Vector v3 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, -1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // bottom, near, right point
    	Vector v4 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, -1 * UNIT_SCALE, 1 * UNIT_SCALE, 1)))); // bottom, near, left point
    	Vector v5 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, 1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // top, far, left point
    	Vector v6 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, 1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // top, far, right point
    	Vector v7 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(1 * UNIT_SCALE, -1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // bottom, far, right point
    	Vector v8 = rotX().multiply(rotY().multiply(CTM.multiply(new Vector(-1 * UNIT_SCALE, -1 * UNIT_SCALE, -1 * UNIT_SCALE, 1)))); // bottom, far, left point

    	drawTri(v1, v2, v3); drawTri(v1, v3, v4); // near facing
    	drawTri(v5, v6, v7); drawTri(v5, v7, v8); // far facing
    	drawTri(v1, v6, v2); drawTri(v5, v6, v1); //top facing
    	drawTri(v4, v8, v7); drawTri(v7, v4, v3); //bottom facing
    	drawTri(v1, v4, v8); drawTri(v8, v5, v1); //left facing
    	drawTri(v2, v6, v7); drawTri(v7, v3, v2); //right facing
    	
    }
    
    private void drawLine(Vector iv1, Vector iv2) {
    	Vector v1, v2;
    	v1 = lookat.multiply(iv1);
    	v2 = lookat.multiply(iv2);
    	
    	if(ortho_view) {
    		v1 = ortho.multiply(v1); 
    		v2 = ortho.multiply(v2);
    	} else {
    		v1 = frustum.multiply(v1);
    		v2 = frustum.multiply(v2);
    	}

       	ArrayList<Vector> theLine = traceLine(v1, v2);
    	
    	for(Vector p : theLine) {
    		drawPixel((int) p.x, (int) p.y, p.z, red, green, blue);
    	}
    }
    
    private class PointCompare implements Comparator<Vector> {

    	public int compare(final Vector a, final Vector b) {
        	return Double.compare(a.y, b.y);
    	}
    }
    
    private ArrayList<Vector> traceLine(Vector v1, Vector v2) {
    	Vector first = v1;
    	Vector second = v2;
    	if(v1.x > v2.x) { // order points from left to right.
    		first = v2;
    		second = v1;
    	}
    	ArrayList<Vector> thepoints = new ArrayList<Vector>();
    	
    	double dy = second.y - first.y;
    	double dx = second.x - first.x;
    	
    	double mag_slope = Double.MAX_VALUE;
    	if(dx != 0) mag_slope = Math.abs((height * dy) / (width * dx)); // use this to determine whether we draw along x or y;
    	boolean draw_across = mag_slope <= 1.0;
    	boolean down_slope = (second.y < first.y);
    	int change_slope = 1;
    	if(down_slope) change_slope = -1;
    	
    	int startX = worldToScreenWidth(first.x);
    	int endX = worldToScreenWidth(second.x);
    	int startY = worldToScreenHeight(first.y);
    	int endY = worldToScreenHeight(second.y);
    	
    	if(dx == 0 && down_slope) 
    		for(int i = startY; i >= endY; i--) 
    			thepoints.add(new Vector(startX, i, dzdy(first, second, i), 1)); 
    	if(dx == 0 && !down_slope)
    		for(int j = startY; j <= endY; j++)
    			thepoints.add(new Vector(startX, j, dzdy(first, second, j), 1));
    	
    	if(draw_across) {
    		double decider = 2 * (Math.abs(dy) * height) - (dx * width);
    		int currentY = startY;
    		for(int k = startX; k <= endX; k++) {
    			if(decider >= 0) {
    				int oldY = currentY;
    				currentY += change_slope;
    				if(currentY < Math.min(startY, endY) || currentY > Math.max(startY, endY)) currentY= oldY;
    			}
    			thepoints.add(new Vector(k, currentY, dzdx(first, second, k), 1));
    			decider = bresenhamDecider(decider, dx * width, Math.abs(dy) * height);
    		}
    	} else {
    		double decider = 2 * (dx) * height - Math.abs(dy * width);
    		int currentX = startX;
    		if(down_slope) {
    			for(int k = startY; k >= endY; k--) {
    				if(decider >= 0) {
    					int oldX = currentX;
    					currentX++;
    					if(currentX > endX) currentX = oldX;
    				}
    				thepoints.add(new Vector(currentX, k, dzdy(first, second, k), 1)); 
    				decider = bresenhamDecider(decider, Math.abs(dy) * height, dx * width);
    			}
    		} else {
    			for(int k = startY; k <= endY; k++) {
    				if(decider >= 0) {
    					int oldX = currentX;
    					currentX++;
    					if(currentX > endX) currentX = oldX;
    				}
    				thepoints.add(new Vector(currentX, k, dzdy(first, second, k), 1)); 
    				decider = bresenhamDecider(decider, Math.abs(dy) * height, dx * width);
    			}
    		}
    	}
    	return thepoints;
    }
    
    private int convertRGB(final double v) {
    	return (int) (v * MAX_RGB);
    }
    
    private int worldToScreenWidth(final double v) {
    	return (int) ((width - 1) * (v + 1) / 2);
    }
    
    private int worldToScreenHeight(final double v) {
    	return (int) ((height - 1) * (v + 1) / 2);
    }
    
    // dz/dx calculation
    protected static double dzdx(Vector a, Vector b, double cur_x) {
    	double value = Math.max(a.z, b.z); // verify with frustum/ortho projection
    	if((int) a.x != (int) b.x) // divide by zero
    		value = cur_x * (b.z - a.z) / (b.x - a.x);
    	return value;
    }
    
    // dz/dy calculation (may not need)
    protected static double dzdy(Vector a, Vector b, double cur_y) {
    	double value = Math.max(a.z,  b.z); // verify with frustum/ortho projection
    	if((int) a.y != (int) b.y) // divide by zero
    		value = cur_y * (b.z - a.z) / (b.y - a.y);
    	return value;
    }
    
    // if negative use old y, otherwise use y + 1
    private double bresenhamDecider(final double prev, final double dx, final double dy) {
    	double value = 0;
    	if(prev < 0) value = prev + 2 * dy;
    	else value = prev + (2 * dy) - (2 * dx);
    	return value;
    }


    
    /**
     * Creates a matrix for translation along an x, y, z vector.
     * 
     * @param x the x-related translation.
     * @param y the y-related translation.
     * @param z the z-related translation.
     * @return the translation matrix.
     */
    private Matrix translateMatrix(double x, double y, double z) {
    	ArrayList<Vector> moving = new ArrayList<Vector>();
    	moving.add(new Vector(1, 0, 0, x));
    	moving.add(new Vector(0, 1, 0, y));
    	moving.add(new Vector(0, 0, 1, z));
    	moving.add(new Vector(0, 0, 0, 1));
    	return new Matrix(moving);
    	
    }
    
    /**
     * Creates a matrix for scaling as specified along x, y, z.
     * 
     * @param x the scale on the x-axis.
     * @param y the scale on the y-axis.
     * @param z the scale on the z-axis.
     * @return the resulting scaling matrix.
     */
    private Matrix scaleMatrix(double x, double y, double z) {
    	ArrayList<Vector> scaling = new ArrayList<Vector>();
    	scaling.add(new Vector(x, 0, 0, 0));
    	scaling.add(new Vector(0, y, 0, 0));
    	scaling.add(new Vector(0, 0, z, 0));
    	scaling.add(new Vector(0, 0, 0, 1));
    	return new Matrix(scaling);
    }
    
    /**
     * Generic rotation about the y axis.
     * 
     * @param value the value to rotate by.
     * @return the rotational transformation matrix.
     */
    private Matrix rotY(double value) {
    	ArrayList<Vector> build = new ArrayList<Vector>();
    	build.add(new Vector(Math.cos(Math.toRadians(value)), 0, Math.sin(Math.toRadians(value)), 0));
    	build.add(new Vector(0, 1, 0, 0));
    	build.add(new Vector(Math.sin(Math.toRadians(value)) * -1.0, 0, Math.cos(Math.toRadians(value)), 0));
    	build.add(new Vector(0, 0, 0, 1));
    	return new Matrix(build);
    }
    
    /**
     * User controlled rotation changes to the model.
     * @return the transformation dictated by the user controls.
     */
    private Matrix rotY() {
    	return rotY(roty);
    }
    
    /**
     * Generic rotation about the x axis.
     * 
     * @param value the value to rotate by.
     * @return the rotational transformation matrix.
     */
    private Matrix rotX(double value) {
    	ArrayList<Vector> build = new ArrayList<Vector>();
    	build.add(new Vector(1, 0, 0, 0));
    	build.add(new Vector(0, Math.cos(Math.toRadians(value)), Math.sin(Math.toRadians(value)) * -1.0, 0));
    	build.add(new Vector(0, Math.sin(Math.toRadians(value)), Math.cos(Math.toRadians(value)), 0));
    	build.add(new Vector(0, 0, 0, 1));
    	return new Matrix(build);
    }
    
    /**
     * User controlled rotation changes to the model.
     * @return the transformation dictated by user controls.
     */
    private Matrix rotX() {
    	return rotX(rotx);
    }
    
    public void paintComponent(Graphics g) {
    	try {
    		createImage();
    	} catch(Exception e) {
    		System.err.printf("Unexpected error.  %s hits paintComponent from createImage().\n", e.getClass().getName());
    	}
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        WritableRaster raster = image.getRaster();
        raster.setPixels(0, 0, width, height, pixels);        
        g.drawImage(image, 0, 0, null);
    }
    
    
    public void keyPressed(KeyEvent e){
        if (e.getKeyCode() == KeyEvent.VK_UP) {
            if(rotx >= 359) rotx -= 360;
            rotx += DEGREE_SHIFT;
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            rotx -= DEGREE_SHIFT;
        	if(rotx >= 0) rotx += 360;
        } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
        	if(roty >= 359) roty -= 360;
            roty += DEGREE_SHIFT;
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {  
        	roty -= DEGREE_SHIFT;
        	if(roty >= 0) roty += 360;
        }
        repaint(); 
    }

    public void keyReleased(KeyEvent e){
    }

    public void keyTyped(KeyEvent e) { 
    	if(e.getKeyCode() == KeyEvent.VK_P) ortho_view = false;
    	else if (e.getKeyCode() == KeyEvent.VK_O) ortho_view = true; 
    	else if(e.getKeyChar() == 'l') {
    		left = left - 0.1 * left;
    	} else if(e.getKeyChar() == 'L') {
    		left = left + 0.1 * left;
    	}
    	else if(e.getKeyChar() == 'r') {
    		right = right + 0.1 * right;
    	} else if(e.getKeyChar() == 'R') {
    		right = right - 0.1 * right;
    	}
    	else if(e.getKeyChar() == 't') {
    		top = top + 0.1 * top;
    	} else if(e.getKeyChar() == 'T') {
    		top = top - 0.1 * top;
    	}
    	else if(e.getKeyChar() == 'b') {
    		bottom = bottom - 0.1 * bottom;
    	} else if(e.getKeyChar() == 'B') {
    		bottom = bottom + 0.1 * bottom;
    	}
    	else if(e.getKeyChar() == 'n') {
    		near = near + 0.1 * near;
    	} else if(e.getKeyChar() == 'N') {
    		near = near - 0.1 * near;
    	}
    	else if(e.getKeyChar() == 'f') {
    		far = far - 0.1 * far;
    	} else if(e.getKeyChar() == 'F') {
    		far = far + 0.1 * far;
    	}    	
    	setFrustum();
    	setOrtho();
    	repaint();
    }        

    public static void main(String args[]) {
    	PaintPanel458 runtime = new PaintPanel458();
    	if(runtime.getInput()) {
    		JFrame frame = new JFrame("Paint Demo");
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.getContentPane().add(runtime);
    		frame.pack();      
    		frame.setLocationRelativeTo( null );
    		frame.setVisible( true );
    	}
    }

}
